  // -*- mode:C; tab-width:2; indent-tabs-mode:nil;  -*-
// $Id: eventManager.c,v 1.22 2010/04/21 00:23:27 zahorjan Exp $

#include <stdlib.h>
#include <string.h>

#include "eventManager.h"
#include "queue.h"
#include "hashTable.h"
#include "lockDef.h"
#include "factory.h"
#include "taskQueue.h"
#include "atomicInt.h"

#define HASH_TABLE_SIZE 10

INSTANTIATETYPE(handler_data_t,  
                eventName_t eventName; 
                eventHandler_fn_t fn;
                eventHandler_closure_t onData;
                eventManager_onData_destructor_t onDataDestructorFn;)
INSTANTIATETYPE(eventManager_t, 
                taskQueue_t eventQueue; hashTable_t callbacks; int isDestroyed; pthread_mutex_t mgrLock;)

INSTANTIATETYPE(ourFnCl, eventName_t eventName; eventHandler_fn_t fn; eventHandler_closure_t onData; eventHandler_closure_t fireData; eventManager_firecallback_fn_t postFireFn; eventManager_allfiredcallback_fn_t allFireFn; atomic_int_t atomCounter;)

INSTANTIATETYPE(addTaskHelper, taskQueue_t tq; eventHandler_closure_t fireData; eventManager_firecallback_fn_t preFireFn; eventManager_firecallback_fn_t postFireFn;
                eventManager_allfiredcallback_fn_t allFireFn; atomic_int_t atomCounter;) 

CREATEMANAGER(handler_data_t)

CREATEMANAGER(eventManager_t)

CREATEMANAGER(ourFnCl)

CREATEMANAGER(addTaskHelper)

taskQueue_taskResult_t ourFn(ourFnCl c) {
  c->fn(c->onData,c->fireData);
  return 0;
}

int eventManager_onEvent( eventManager_t mgr,
                          eventName_t eventName,
                          eventHandler_fn_t fn,           // the handler function
                          eventHandler_closure_t onData,  // data to be passed to it each time this event is fired
                          eventManager_onData_destructor_t onDataDestructorFn ){

  pthread_mutex_lock(&(mgr->mgrLock));
  if (!mgr->isDestroyed) {
    queue_t handler_queue = lookupHashItem(mgr->callbacks,eventName);
    if (handler_queue==NULL){//key does not exist
      handler_data_t handler_data = handler_data_t_acquire();
      handler_data->eventName = eventName;
      handler_data->fn = fn;
      handler_data->onData = onData;
      handler_data->onDataDestructorFn = onDataDestructorFn;
      queue_t q = queue_create();
      queue_append(q, handler_data);
      addHashItem(mgr->callbacks, eventName, q, NULL,NULL );
    }else{
      handler_data_t handler_data = handler_data_t_acquire();
      handler_data->eventName = eventName;
      handler_data->fn = fn;
      handler_data->onData = onData;
      handler_data->onDataDestructorFn = onDataDestructorFn;
      queue_append(handler_queue, handler_data);    
    }
  pthread_mutex_unlock(&(mgr->mgrLock));
    return 0;
  } else {
    return 1;
  }
}

int hash_function(void* k){
 int sum=0;
 int len = strlen((char *)k);
 int i;
 char* key = (char*) k;
 for(i=0; i < len; i++){
   sum =  sum + (int)key[i];
 }
 return sum;
}

int hash_function_compare(void* keyA, void* keyB){
       if (strcmp((char *)keyA,(char *)keyB)==0) return 0;
       return 1;
}

void handler_destructor(handler_data_t h) {
  if (h->onDataDestructorFn)
    h->onDataDestructorFn(h->onData);
  handler_data_t_release(h, NULL);
}

hash_value_destroy_fn_t value_destroy_fn(queue_t q){
  queue_destroy(q, handler_destructor);
  return 0;
}

//remove all tasks from taskqueue before destroying (releasing) memory

int eventManager_create(eventManager_t* mgr, int nWorkers) {
 *mgr = eventManager_t_acquire();
 //passing in the address of the mgr->eventQ so that taskQueue_create can initialize it
 taskQueue_create(&((*mgr)->eventQueue), nWorkers);
 //create 'callbacks' hash table; onEvents will be added later
 (*mgr)->callbacks = hashTable_create(HASH_TABLE_SIZE, hash_function, hash_function_compare);
 (*mgr)->isDestroyed = 0; 
 pthread_mutex_init(&((*mgr)->mgrLock), NULL);
 return 0;
}

void ourDoneFn(taskQueue_closure_t c, taskQueue_taskResult_t taskResult){
  if (((ourFnCl)c)->postFireFn) {
    ((ourFnCl)c)->postFireFn(((ourFnCl)c)->eventName, 
                                  ((ourFnCl)c)->onData, 
                                   ((ourFnCl)c)->fireData);
    }
  atomic_int_postDecrement(((ourFnCl)c)->atomCounter); 
  //printf("Decremented atomCounter, value is %d\n", atomic_int_getValue( ((ourFnCl)c)->atomCounter));
  if (atomic_int_getValue( ((ourFnCl)c)->atomCounter) == 0) {
    if ( ((ourFnCl)c)->allFireFn) {
      ((ourFnCl)c)->allFireFn(((ourFnCl)c)->eventName, ((ourFnCl)c)->fireData);
    }
    atomic_int_t_release( ((ourFnCl)c)->atomCounter, NULL);
  }
  ourFnCl_release((ourFnCl)c, NULL);
}

// this is the 'apply' function for our queue to use
boolean_t addTask(queue_element_t e, queue_pfapply_closure_t cl) {
 ourFnCl ourClosure = ourFnCl_acquire();
 ourClosure->eventName = ((handler_data_t)e)->eventName;
 ourClosure->fn = ((handler_data_t)e)->fn;
 ourClosure->onData = ((handler_data_t)e)->onData;
 ourClosure->fireData = ((addTaskHelper)cl)->fireData;
 ourClosure->postFireFn = ((addTaskHelper)cl)->postFireFn;
 ourClosure->atomCounter = ((addTaskHelper)cl)->atomCounter;
 ourClosure->allFireFn = ((addTaskHelper)cl)->allFireFn;
 if (((addTaskHelper)cl)->preFireFn)
  ((addTaskHelper)cl)->preFireFn(((handler_data_t)e)->eventName, ourClosure->onData, ourClosure->fireData);
 taskQueue_addTask(((addTaskHelper)cl)->tq, ourFn, ourClosure, ourDoneFn);
 return TRUE;
}

void eventManager_fireEvent( eventManager_t mgr, eventName_t eName, eventHandler_closure_t fireData, eventManager_firecallback_fn_t preFireFn, eventManager_firecallback_fn_t postFireFn, eventManager_allfiredcallback_fn_t allFireFn) {
  pthread_mutex_lock(&(mgr->mgrLock));
  if (mgr) {
    if (!mgr->isDestroyed) {  
      queue_t q = lookupHashItem(mgr->callbacks, eName);
      atomic_int_t a; 
      atomic_int_create(&a, queue_size(q));
      //printf("a value is %d\n", atomic_int_getValue(a));
      addTaskHelper temp = addTaskHelper_acquire();
      temp->tq = mgr->eventQueue;
      temp->fireData = fireData;
      temp->preFireFn = preFireFn;
      temp->postFireFn = postFireFn;
      temp->atomCounter = a;
      temp->allFireFn = allFireFn;
      queue_apply(q, addTask, temp);
      addTaskHelper_release(temp, NULL);
    }
  } else {
    return 0;
  }
  pthread_mutex_unlock(&(mgr->mgrLock));
}

int eventManager_destroy(eventManager_t mgr) {
  pthread_mutex_lock(&(mgr->mgrLock));
  mgr->isDestroyed = 1;
  // this will cause destroying to wait until all tasks are done
  taskQueue_destroy(mgr->eventQueue);
  hashTable_destroy(mgr->callbacks, NULL, value_destroy_fn);
  pthread_mutex_unlock(&(mgr->mgrLock));
  eventManager_t_release(mgr, NULL);
  return 0;
}
